Skip to content

feat: add scheduler backend (incomplete) - #1282

Draft
kvlonge wants to merge 182 commits into
mainfrom
feat/orchestration-backend
Draft

kvlonge wants to merge 182 commits into
mainfrom
feat/orchestration-backend

Conversation

@kvlonge

@kvlonge kvlonge commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Why

Native orchestration, Python workers, browser CLI authentication.

Changes

  • SQLite/PostgreSQL storage, fenced scheduling, immutable snapshots, API/watchdog, audited administration.
  • Pinned workers; atomic groups, frozen retries/backfills, calendars, webhooks, sensors.
  • Bounded histories/reconciliation; full catalog scans authorize removal; reads enforce live View grants.
  • Build evidence/planner identities separate observations, definitions, builds.
  • Metadata-only statistics, --statistics-retention-days (90).
  • Retention: --audit-retention-days (400) drops audit rows all webhooks have and no retained retry uses; skipped/covered ticks expire after 30 days (latest/run-linked kept); none older is created except one latest catch-up tick; --snapshot-retention-count (10) keeps newest/referenced snapshots.
  • Custom roles: narrowing applies on publish; widening needs another admin's approval or a sole admin's confirmed, audited self-approval.
  • Frozen task/asset/check source; checks feed sensors without widening runnable sets.
  • Person connection tests; password/OIDC/proxy roles, revocation, CSRF; offline Postgres recovery.
  • Durable trigger progress, atomic projection repair; missing old facts fail closed.
  • Whole-second cron ticks; old cursors don't re-tick.
  • Single-use browser-approved CLI sessions, OS credential storage.
  • Not yet: project role declarations; node grants, signed approvals, SSO groups; operator backfill past tick retention; non-Linux recovery; consumed-input provenance.

Verification

  • Native 98/98 incl. retention, role approval, scheduler faults/DST/latency/upgrade; CLI 38/38+16 (SQLite/PostgreSQL).
  • Installed publication 94/94, killed schedulers, repair 2/2, login 6/6; Chromium.
  • Compile interruption 109/109; retention/role cases are DuckShop edge cases.
  • make check-ci, generated client check, hygiene scan, bounded review.
  • Dense/progress causes unproved; same budgets.
  • Draft, unmerged, auto-merge off; no release/deploy.

Preserve in-progress storage, scheduling, claims, credentials and native API/CLI work. The orchestration backend remains incomplete and is not ready for delivery.
Add claim-scoped execution inputs and native snapshot drift validation.

Incomplete orchestration backend checkpoint; worker launch and the remaining control-plane integration are not implemented yet. Targeted SQLite/PostgreSQL tests pass; full static readiness is pending.
Verify pinned inputs before start and expose retryable run reports and cancellation heartbeats over the authenticated API. Disable redirects for credentialed requests and cover the lifecycle against SQLite and PostgreSQL. Keep the orchestration PR explicitly incomplete pending actual launchers and the remaining backend scope.
Separate lease expiry, replica connections, terminal run fixtures and fault cases so each integration binary imports only the support it uses. Preserve SQLite and PostgreSQL transaction coverage.
Observe heartbeat loss in fenced, bounded transactions while retaining physical write claims. Recover through existing worker heartbeats and continue full batches without an idle delay.
Own worker heartbeats on a dedicated native runtime and connection pool. Propagate cancellation and revoked credentials without cancelling work on transient API failures; stop and join with the execution owner.
Move bounded client construction, credential redaction, URL validation and JSON response handling to the API owner so worker and location flows can share them. Keep independent heartbeat pools and worker-facing error categories.
Add privileged native administration with JSON results, bounded location credentials and atomic security audit. Detect lost result delivery without claiming committed mutations rolled back.
Keep the public Python API limited to authoring records and native delegation. Resolve selectors and validate schedule data in Rust before publication. Preserve stable shorthand trigger names across cron edits.

Add the explicitly approved narrow Python facade boundary entries without increasing movable Python or native crossing budgets.
Collect exported jobs and nested factory containers natively, deduplicating aliases and terminating cyclic containers. Explicit empty declarations skip user module execution. Remove failed imports from the module cache so publication retries cannot reuse partial definitions.
Verify retained compiler baselines before worker startup and join native heartbeat work on interpreter-side close. Preserve claim identity and report retry keys without treating resource teardown as physical warehouse completion.
Restrict forward and reverse cron expansion and pure tick planning to the scheduler. Preserve scheduling bodies and all existing fault, DST and simulation coverage.
Expose owner-only cancellation with stable retry identity. Commit lifecycle facts, projections and security audit together while retaining active physical claims until termination.
Resolve manual requests against the current published job inside one write transaction. Preserve the accepted snapshot and execution inputs across retries after redeployment, and commit queue publication with the trusted owner audit.
Compile in the project interpreter and retain native snapshots through publication. Keep JSON results separate from progress and distinguish uncertain acknowledgements from failed result delivery.
Jobs and groups coalesce: one run of a job is active at a time and an
unclaimed waiting run covers later due times. Schedules catch up only the
latest missed time and record the rest as covered. A failed node skips
only its lineage dependents in a group; critical nodes still stop it.
The overlap, on_failure, catch_up and max_catch_up options are removed.

The two-scheduler fault test now drives many minutely triggers, since one
trigger's backlog is decided in a single transaction, and checks that each
tick requests or is covered by exactly one run.
Drop the warehouse.sync, plan.preview, location.manage and token.manage
actions, which no route checked. Remove principal-scoped API tokens, the
principal kind and the issue-principal-token command; people
authenticate with sessions and machines with location or worker tokens.

Validated: orchestration lib 94/94 on SQLite and PostgreSQL, CLI native
tests 14/14, TypeScript client regenerated and its test passing.
…e replace

Replace the nine per-kind ingest routes, batch types and client methods
with one metadata endpoint that takes the state-table kind. A full scan
now tags the rows it delivers and its complete page deletes the
namespace's other rows in one statement, replacing the scan receipt and
key tables, page contiguity checks and continuation cursors. Catalog
updates no longer append change history events. The five mirror event
tables stay separate.

Validated: orchestration lib 94/94 on SQLite and PostgreSQL, 50
warehouse metadata, catalog, statistics, source sensor and work
integration tests, TypeScript client regenerated and its test passing.
Replace offline recover-user and its shared/exclusive store access locks
with an online sqb admin grant-admin command. Final-admin protection is a
plain count of other active installation administrators.
Remove the role activation and approval workflow: its requests,
decisions, definition history tables and endpoints. Each published roles
document replaces the target's custom roles, and administrators control
who holds them.
The session middleware authenticates the bearer and CSRF once; person
operations receive the authenticated principal and check live
eligibility and grants under their store lock instead of re-reading the
session.
Password sessions carry their credential version; SSO sessions leave it
empty. Revocation and validation read one table.
The trusted proxy no longer mints browser sessions or throttles its own
peer address. Each person request authenticates from the configured
peer and bound X-SQB-User subject; state-changing requests still need
the configured origin. CLI login flows record the approving person and,
for session approvals, the session that must stay live until exchange.
Anonymous authentication denials and login reservations no longer write
to the durable security audit, so unauthenticated callers cannot force
audit writes; the response still carries a decision ID. Authorization
denials of signed-in people stay audited.
The OS keyring stays first. Headless Linux falls back to one owner-only
0600 file read without following symlinks; the ancestor ownership and
mode walk is removed.
A session sent in the Authorization header cannot be attached by a
browser, so it needs no CSRF proof. The CLI login exchange no longer
returns one and the CLI no longer stores or sends it; cookie sessions
keep theirs.
ApiAuthentication carries a single HumanLogin choice instead of four
optional settings, so conflicting modes are rejected once when the CLI
reads its flags.
…ests

The store tests inject grant-admin faults at the audit, assignment and
session writes on SQLite and PostgreSQL. The installed PostgreSQL test
now covers only what they cannot: live revocation across two running
replicas and the HTTP final-admin refusal. The upgrade fixture left
behind by the single schema file is removed.
A trusted proxy that forwards its own Authorization header, such as
Basic credentials, had every request routed to session authentication
and denied. Only an Authorization header with the Session scheme now
selects session authentication, in proxy mode and when approving a CLI
login.
Authentication denials are not audited, so their identifier matches no
audit decision. The denial body now carries request_id, the request's
own ID, and password login no longer derives a per-attempt decision ID.
Stale comments about replay, upgrades and approvals are updated.
Run nodes are keyed by run and node, since every attempt was 1. Ticks
lose the failed and skipped outcomes and the reason column, which were
never written, and runs lose the presumed_failed status, which no code
set; a sensor could declare it but never fire. The planner's disabled
state, always enabled, and the schema's minimum server version, which
the checksum already covers, are removed. Launch failures stay because
platform sensors can subscribe to them.
…y cover

Trigger changes are recorded only in the security audit. Its scope keeps
the authored request for retry checks and for the trigger events
endpoint, which reads through a partial index. The job nodes table,
which nothing read, is removed. Bootstrap is open only while no person
exists, replacing the administrator initialization marker; grant-admin
makes any existing person an administrator.
…le rows

A manual job or rerun records who asked and what they sent on its run
row, so a retry with the same run ID is matched there instead of in the
security audit, and audit retention no longer keeps rows for retries.
Role assignments hold only current grants: revoking deletes the row, and
the audit keeps who granted or revoked it and when.
The latest cursor lives on sqb_trigger_state.cursor_json and evaluation
history lives only in sqb_sensor_evaluation_events, pruned by the
maintenance task after seven days once a later attempt exists. The
sqb_sensor_cursor_commits table and the recall_events evaluation copy go.
A schedule evaluation now looks up the latest due occurrence after its
cursor and records one tick for it: a requested run, or a covered tick
when an unclaimed run is already waiting. Earlier missed times are no
longer expanded or recorded, which removes bounded expansion, the
continuation cursor and the retention horizon at evaluation time.

The fault suite now requires every trigger's latest due time to tick
once after recovery, alongside the duplicate, off-schedule and
run-to-tick checks. A ten-minute stepped DST simulation still checks
that every cron occurrence ticks exactly once.
…bound platform state

The source-new-data sensor pages its selected sources and reads each
one's latest sqb_warehouse_freshness row, queueing one run per change
from the version received before it. The recall_events copy of every
observation and its partial index go.

Platform alerts are keyed by their condition rather than the 30-second
scan window, so a persisting condition is recorded once. The one
maintenance task prunes alerts after 30 days, heartbeats after two,
and expired location work and unseen location instances after seven.
A source's run key used the newest earlier row with a different version.
When a complete scan pruned those rows, a version that returned to an
earlier value produced the same key as its first appearance and queued
no run. The key now uses the first row of the latest version's current
streak, so pruning can only move it later and never reuses a key.
Brings fensu 0.29 with the unread-state, unconstructed-variant and
new-duplicate gates, pinned internal crate versions, and the variant
cleanup. The verify workflow keeps the separate native-checks job and
the generated-client check, and gains the duplicate-check base fetch.
…sions

Internal crates now carry a fixed 0.0.0, so control events no longer
read CARGO_PKG_VERSION. The CLI records its release version once when
it opens a control store, and the lock file carries the pinned version.
The control store's Recall views take main's new DDL option, which only
the version row uses.
Remove control-store state that no production query reads, so the
unread-state rule reports nothing for the scheduler:

- snapshot schema version, current-snapshot since, trigger-state
  updated_at, concurrency-slot and write-claim claimed_at, access-token
  claim_id (the claim stays in the token scope), location-instance
  connected_at;
- source_event_id on the freshness, audit-result, janitor, migration,
  old-name-view and microbatch mirrors (the record keeps the warehouse
  event ID; fingerprints keep their column because reads use it);
- janitor relation database, schema and archive name, old-name-view
  destination model, and the microbatch generation, origin-run and
  replay-requirement IDs.

Allowlist entries left with reasons: run-request evaluation IDs and
snapshot git authors are read by the operations UI, and write
claims are read through their primary-key conflict. Stale entries for
state already removed are dropped.
…zation

Several control-store operations repeated the same steps by hand. They
now go through one owner each:

- write runs one operation in its own writer transaction and commits it;
- query_records and read_records query and decode typed records;
- begin_authorized opens the writer transaction and authorizes the
  person in it, replacing the warehouse-metadata begin_view copy and the
  inline begin-and-authorize in runs, audit and administrator changes;
- PermissionScope::action builds a scope with no node restriction;
- Parameter::placeholders numbers IN-list and VALUES placeholders;
- snapshot documents and nodes share one keyed body query, and catalog
  and statistics pages share their database scope and keyset cursor;
- password and OIDC sign-in share the live-person check before issuing
  a session;
- retention drains every policy through one page-per-transaction loop.

Behaviour is unchanged; location work is now drained before location
instances instead of interleaved with them.
Location commands, worker completion and workspace commands each had a
copy of "write to a sys stream and flush". They now use write_text in
the Recall console module, which write_stderr already lived in. The
native worker's lock-and-check-open preamble moves into with_owner.
The workspace commands file has fewer Python crossings, so its pending
budget is lowered to match.
The new-duplicate gate compares the whole scheduler stack against main.
After the shared helpers, what it still reports are parallel shapes,
not copied logic: one annotated axum handler per OpenAPI operation,
one store read per record type, enum string tables, redacting Debug
implementations, per-dialect view lists, binding registrations and
the two directions of the cron search. Each group gets an allowlist
entry naming why it stays separate and which shared owner already
holds the common part.
Compiled-project bindings sit below Recall, so the shared stream writer
moves back to the native host module and Recall's stderr writer calls
it. The Recall console file loses crossings, so its pending budget is
lowered to match. The trusted run listing joins the store-read
allowlist entry it now resembles.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant